/*
 *  Copyright (C) 2010 Zubanov Dmitry
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.jssdb.core.net;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.jssdb.core.event.CustomEvent;
import org.jssdb.core.event.CustomEventListener;
import org.jssdb.core.filesystem.FilesListInfo;
import org.jssdb.core.filesystem.Transaction;

/**
 *
 * @author developer
 */
public class MulticastServer {

    private DatagramSocket socket = null;
    private String multicastAddress = "230.0.0.1";
    private int port = 5600;
    private boolean hasMore = true;
    private DataChunkCollector dataChunkCollector = DataChunkCollector.getInstance();
    private static final Logger logger = Logger.getLogger(MulticastServer.class.getName());
    private Set<ServerInfo> serversSet;

    private MulticastServer() {
        serversSet = new HashSet();
        dataChunkCollector.addCustomEventListener(new CustomEventListener() {

            public void customEventOccurred(CustomEvent evt) {
                eventHandler(evt.getSource());
            }
        });
    }

    public void start(){
        Executors.newSingleThreadExecutor().execute(reciever());
    }

    private void eventHandler(Object obj) {
        if (obj == null) {
            return;
        }

        if (obj instanceof MulticastCommand) {
            MulticastCommand cmd = (MulticastCommand) obj;
            if (cmd.getCommand() == MulticastCommand.GET_INFO) {
                ServerInfo serverInfo = new ServerInfo();
                serverInfo.setHost(ReplicationServer.getInstance().getHost());
                serverInfo.setPort(ReplicationServer.getInstance().getPort());
                try {
                    sendRequest(serverInfo);
                    //sendRequest(new BigObject(1000));
                } catch (Exception ex) {
                    logger.log(Level.SEVERE, multicastAddress, ex);
                }
            }
        }

        if (obj instanceof ServerInfo) {
            ServerInfo serverInfo = (ServerInfo) obj;
            if (!serverInfo.getHost().equals(ReplicationServer.getInstance().getHost())) {
                ReplicationServer.getInstance().sender(serverInfo.getHost(),
                        serverInfo.getPort() - 1, FilesListInfo.getInstance());
            }
        }

        if (obj instanceof Transaction) {
            Transaction transaction = (Transaction)obj;
            transaction.makeTransaction();
        }

        // test
        //if(obj instanceof BigObject){
        //    System.out.println(((BigObject) obj).getList());
        //}
    }

    public void setPort(int port) {
        this.port = port;
    }

    public void setMulticastAddress(String address) {
        multicastAddress = address;
    }

    public ArrayList<ServerInfo> getServerInfoList() {
        if (serversSet.isEmpty()) {
            return null;
        }
        ArrayList<ServerInfo> list = new ArrayList();
        boolean addAll = list.addAll(serversSet);
        return list;
    }

    public void sendRequest(Object obj) throws Exception {
        if (socket == null) {
            socket = new DatagramSocket(port - 1);
        }

        ArrayList<DataChunk> dataChunkList = new DataChunkList().getDataChunkList(obj);

        for (DataChunk dataChunk : dataChunkList) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            GZIPOutputStream gz = new GZIPOutputStream(baos);
            ObjectOutputStream oout = new ObjectOutputStream(gz);
            oout.writeObject(dataChunk);
            oout.flush();
            oout.close();
            gz.flush();
            gz.close();

            InetAddress group = InetAddress.getByName(multicastAddress);
            DatagramPacket packet = new DatagramPacket(baos.toByteArray(),
                    baos.toByteArray().length, group, port);
            socket.send(packet);
            logger.log(Level.INFO, "Multicast server send object ({0})", obj);
        }

    }

    private Runnable reciever() {
        Runnable r = new Runnable() {

            public void run() {
                try {
                    MulticastSocket multicastSocket = new MulticastSocket(port);
                    InetAddress broadcast = InetAddress.getByName(multicastAddress);
                    multicastSocket.joinGroup(broadcast);
                    DatagramPacket packet;
                    while (hasMore) {
                        byte[] buf = new byte[65536];
                        packet = new DatagramPacket(buf, buf.length);

                        multicastSocket.receive(packet);
                        byte[] b = packet.getData();
                        if (b.length == 0) {
                            continue;
                        }

                        ByteArrayInputStream bais = new ByteArrayInputStream(b);
                        GZIPInputStream gz = new GZIPInputStream(bais);
                        ObjectInputStream in = new ObjectInputStream(gz);
                        Object object = in.readObject();
                        logger.log(Level.INFO, "Multicast reciever get object ({0})", object);

                        if (object instanceof DataChunk) {
                            dataChunkCollector.addChunk((DataChunk) object);
                        }
                    }
                } catch (Exception e) {
                    logger.log(Level.SEVERE, null, e);
                }
            }
        };
        return r;
    }

    public static MulticastServer getInstance() {
        return MulticastServerHolder.INSTANCE;
    }

    private static class MulticastServerHolder {

        private static final MulticastServer INSTANCE = new MulticastServer();
    }
}
